#pragma once

#include <string>
#include <functional>
#include <vector>
#include <memory>
#include <thread>
#include "../spsc_queue.hpp"
#include "../timer_queue.hh"
#include "../singleton.hpp"
#include "../util/allocator.hh"

#ifdef WIN32
#include "thirdparty/include/hiredis.h"
#else
#include <hiredis/hiredis.h>
#endif // WIN32

namespace kratos { namespace redis {

class RedisConn;
class Redis;
class RedisResult;

// Redis connection query policy, reserved
enum class HostFindPolicy : std::uint32_t {
    REDIS_HOST_FIND_RANDOM = 1,
    REDIS_HOST_FIND_ROUNDROBIN,
};

// Redis connection manager
class Redis {
    using ConnVector = std::vector<RedisConn*>;
    using HostMap = std::unordered_map<std::string, ConnVector>;
    HostMap hostMap_; // host connection map

public:
    // ctor
    Redis();
    // dtor
    ~Redis();
    // main cycle
    // @param now current timestamp in millionseconds
    void update(std::time_t now);
    // add host via name
    // @param name host name
    // @param host host like 127.0.0.1:6379, port is 6379 by default
    // @param password password
    // @param maxPoolSize The maximum size of connection pool
    // @retval true
    // @retval false fail
    bool addHost(const std::string& name, const std::string& host, const std::string& password = "", std::size_t maxPoolSize = 0);
    // remove host
    // @param name host name
    // @retval true
    // @retval false fail
    bool removeHost(const std::string& name);
    // find a connection
    // @param hostName host name
    // @param policy reserved
    // @return redis connection
    RedisConn& findConn(const std::string& hostName, HostFindPolicy policy = HostFindPolicy::REDIS_HOST_FIND_ROUNDROBIN);

private:
    // verify ip:port
    // @param host ip:port
    // @retval true
    // @retval false fail
    bool checkHost(const std::string& host);
};

// Redis command result
class RedisResult {
    redisReply* reply_; // Redis reply
    std::uint32_t taskID_; // task ID
    std::uint64_t userData_; // user defined data
    std::uint32_t sequence_; // task sequence
    std::string errorString_; // error string
    std::string redisCommand_; // Redis command

public:
    // ctor
    // @param taskID user defined task ID
    // @param userData user defined data to distinguish different source with same taskID
    // @param sequence internal sequence number for the Redis command which generates the result
    RedisResult(std::uint32_t taskID, std::uint64_t userData, std::uint32_t sequence);
    // dtor
    ~RedisResult();
    // returns command
    const std::string& getCommand();
    // returns reply
    redisReply* getReply();
    // returns taskID
    std::uint32_t getTaskID();
    // returns userData
    std::uint64_t getUserData();
    // returns error string
    const std::string& getErrorString() const;
    // Is ok?
    operator bool();

private:
    // set command
    void setCommand(const std::string& redisCommand);
    // reset result
    void reset(redisReply* reply);
    // set error string
    void setErrorString(const std::string& errorString);
    // returns sequence
    std::uint32_t getSequence();

    friend class RedisConn;
};

using TaskFinishMethod = std::function<void(RedisResult&, RedisConn&)>;
using TaskTimeoutMethod = std::function<void(RedisResult&, RedisConn&)>;
using TaskErrorMethod = std::function<void(RedisResult&, RedisConn&)>;

// redis connection
class RedisConn {
    std::string lastError_; // last error string
    std::string host_; // host
    std::string password_; // password
    redisContext* ctx_; // Redis context
    using TimerMap = std::unordered_map<corelib::TimerID, std::uint32_t>;
    struct Methods {
        TaskFinishMethod finishMethod; // invoking when finished
        TaskTimeoutMethod timeoutMethod; // invoking when timeout
        TaskErrorMethod errorMethod; // invoking when some error occurs
        corelib::TimerID timerID; // timer ID
        RedisResult* result; // The result of Redis command
    };
    using CbMap = std::unordered_map<std::uint32_t, Methods>;
    using SPSCQueue = corelib::SPSCQueue<RedisResult*, 1024 * 128>;
    SPSCQueue input_; // input queue
    SPSCQueue output_; // output queue
    CbMap cbMap_; // callback routine map
    TimerMap timerMap_; // timer map
    bool run_; // running flag
    std::unique_ptr<std::thread> routine_; // worker thread
    corelib::TimerQueue timerQueue_; // timer queue

public:
    RedisConn(const RedisConn&) = delete;
    RedisConn(RedisConn&&) = delete;
    const RedisConn& operator=(const RedisConn&) = delete;

public:
    // ctor
    // @param host host
    // @param password password
    RedisConn(const std::string& host, const std::string& password);
    // ctor
    RedisConn();
    // dtor
    ~RedisConn();
    // Is ok?
    operator bool();
    // run Redis command and wait for reply
    // @param command redis command
    // @param taskID task ID
    // @param userData user defined data
    // @param timeout task timeout in millionseconds
    // @param finishMethod task completion routine
    // @param timeoutMethod task timeout routine
    // @param errorMethod task error routine
    // @retval true
    // @retval false fail
    bool doCommand(const std::string & command, std::uint32_t taskID, std::uint64_t userData, std::time_t timeout = 0,
        TaskFinishMethod finishMethod = nullptr, TaskTimeoutMethod timeoutMethod = nullptr, TaskErrorMethod errorMethod = nullptr);

private:
    // The main loop running in main thread
    // @param now current timestamp in millionseconds
    void update(std::time_t now);
    // startup
    bool start();
    // cleanup
    bool stop();
    // connect to Redis server
    redisContext* connect();
    // run Redis command and wait for reply
    redisReply* doRedisCommand(const std::string & command);
    // get last error string
    const std::string& getLastError();

    friend class RedisResult;
    friend class Redis;
};

#define RedisRef (*kratos::corelib::Singleton<kratos::redis::Redis>::instance())

}}
